package com.liu.my_blog_system.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.liu.my_blog_system.entity.TbRefreshToken;
import com.liu.my_blog_system.entity.TbUser;
import com.liu.my_blog_system.mapper.TbUserMapper;
import com.liu.my_blog_system.service.TbRefreshTokenService;
import com.liu.my_blog_system.service.TbUserService;
import com.liu.my_blog_system.utils.*;
import com.liu.my_blog_system.vo.LoginUserVo;
import com.wf.captcha.ArithmeticCaptcha;
import com.wf.captcha.GifCaptcha;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.util.ObjectUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * tb_user (TbUser)表服务实现类
 *
 * @author liuchengjie
 * @since 2021-04-07 14:19:04
 */
@Service
@Transactional
public class TbUserServiceImpl extends ServiceImpl<TbUserMapper, TbUser> implements TbUserService {

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;
    @Autowired
    private Random random;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private TaskService taskService;
    @Autowired
    private TbRefreshTokenService refreshTokenService;

    /**
     * 注册管理员
     * @param user
     * @param request
     * @return
     */
    @Override
    public Boolean saveAdmin(TbUser user, HttpServletRequest request) {
        //判断数据库是否有当前用户
        Integer b = baseMapper.selectByUsername(user.getUsername());
        if (b == null) {
            //补充参数
            user.setRoles(Constants.User.ADMIN);
            user.setAvatar(Constants.User.AVATAR);
            user.setIp(request.getLocalAddr());
            //对密码进行加密
            String encode = bCryptPasswordEncoder.encode(user.getPassword());
            user.setPassword(encode);
            //插入到数据库
            int insert = baseMapper.insert(user);
            return insert == 1;
        } else {
            throw new BlogException(20001, "用户名已经存在");
        }
    }
/*获取验证码*/
    @Override
    public void createCaptcha(HttpServletResponse response, String captchaKey) {
        if (ObjectUtils.isEmpty(captchaKey) || captchaKey.length() < 13) {
            return;
        }
        long key;
        key = Long.parseLong(captchaKey);
        // 设置请求头为输出图片类型
        response.setContentType("image/gif");
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        int captchaType = random.nextInt(3);
        Captcha captchaTarget = null;
        if (captchaType == 0) {
            // 三个参数分别为宽、高、位数
            captchaTarget = new SpecCaptcha(200, 60, 5);
        } else if (captchaType == 1) {
            // gif类型
            captchaTarget = new GifCaptcha(200, 60);
        } else {
            // 算术类型
            captchaTarget = new ArithmeticCaptcha(200, 60);
            captchaTarget.setLen(3);
            // 几位数运算，默认是两位
        }

        // 设置字体
        try {
            captchaTarget.setFont(CaptchaUtils.cpatcha_font_type[random.nextInt(CaptchaUtils.cpatcha_font_type.length)]);
            // 有默认字体，可以不用设置
        } catch (IOException e) {
            e.printStackTrace();
        } catch (FontFormatException e) {
            e.printStackTrace();
        }
        // 设置类型，纯数字、纯字母、字母数字混合
        captchaTarget.setCharType(Captcha.TYPE_DEFAULT);
        String content = captchaTarget.text().toLowerCase();
        //保存到redis
        redisTemplate.opsForValue().set(Constants.User.KEY_CAPTCHA_CODE_CONTENT + key, content, 10 * 60, TimeUnit.SECONDS);
        // 输出图片流
        try {
            captchaTarget.out(response.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 发送邮件
     * @param emailaddr
     * @param request
     */
    @Override
    public void sendEmail(String emailaddr, HttpServletRequest request) {
        //1、防止暴力发送，同一个邮箱，间隔要超过30秒发一次，1小时内同一个ip最多只能发10次，短信最多只能发5次
        String remoteAddr = request.getRemoteAddr().replaceAll(":","_");
        Integer remoteSendTimes =(Integer) redisTemplate.opsForValue().get(Constants.User.KEY_EMAIL_SEND_IP + remoteAddr);
        if (!ObjectUtils.isEmpty(remoteSendTimes) && remoteSendTimes > 10) {
            throw new BlogException("发送太频繁,一个小时内只能发送10次");
        }
        Object emailSendTimes = redisTemplate.opsForValue().get(Constants.User.KEY_EMAIL_SEND_ADDRESS + emailaddr);
        if (!ObjectUtils.isEmpty(emailSendTimes)) {
            throw new BlogException("发送太频繁,30s后再发");
        }
        //2、检查邮箱地址是否正确
        boolean isValidEmail = EmailSender.isValidEmail(emailaddr);
        if (!isValidEmail) {
            throw new BlogException("邮箱格式不正确");
        }
        //3、发送验证码,6位数100000-999999
        int code = random.nextInt(900000) + 100000;
//        Boolean flag = EmailSender.sendText(emailaddr, "验证码", code + " " + "有效时间为5分钟");
        taskService.sendEmailVerifyCode(emailaddr, "验证码", code + " " + "有效时间为10分钟");
        //做记录
        if (remoteSendTimes == null) {
            remoteSendTimes = 0;
        }
        remoteSendTimes++;
        redisTemplate.opsForValue().set(Constants.User.KEY_EMAIL_SEND_IP + remoteAddr, remoteSendTimes, 60 * 60, TimeUnit.SECONDS);
        redisTemplate.opsForValue().set(Constants.User.KEY_EMAIL_SEND_ADDRESS + emailaddr, "send", 30, TimeUnit.SECONDS);
        redisTemplate.opsForValue().set(Constants.User.KEY_EMAIL_CODE_CONTENT + emailaddr, code, 10 * 60, TimeUnit.SECONDS);
    }

    /**
     * 普通用户注册
     * @param user
     * @param emailCode
     * @param captchaCode
     * @param captchaKey
     * @param request
     * @return
     */
    @Override
    public Result register(TbUser user,String emailCode,String captchaCode,String captchaKey, HttpServletRequest request) {
        //检查当前用户是否已经注册
        Integer isRegister = baseMapper.selectByUsername(user.getUsername());
        if (isRegister != null && isRegister == 1) {
            return Result.error().message("用户名已经被注册");
        }
        //检查邮箱格式是否正确
        boolean validEmail = EmailSender.isValidEmail(user.getEmail());
        if (!validEmail) {
            return Result.error().message("邮箱格式不正确");
        }
        //检查该邮箱是否已经注册
        Integer isEmailExist = baseMapper.selectByEmail(user.getEmail());
        if (isEmailExist != null && isEmailExist == 1) {
            return Result.error().message("该邮箱已经被注册");
        }
        //检查邮箱验证码是否正确
        Integer e_code = (Integer) redisTemplate.opsForValue().get(Constants.User.KEY_EMAIL_CODE_CONTENT + user.getEmail());
        if (Integer.parseInt(emailCode) != e_code) {
            return Result.error().message("邮箱验证码不正确或已经过期");
        }
        //检查图灵验证码是否正确
        String captcha_content = (String) redisTemplate.opsForValue().get(Constants.User.KEY_CAPTCHA_CODE_CONTENT + captchaKey);
        if (!captcha_content.equalsIgnoreCase(captchaCode)) {
            return Result.error().message("图灵验证码不正确或已经过期");
        }
        //对密码进行加密
        String encode = bCryptPasswordEncoder.encode(user.getPassword());
        user.setPassword(encode);
        //补全数据
        user.setRoles(Constants.User.USER);
        user.setAvatar(Constants.User.AVATAR);
        user.setIp(request.getLocalAddr());
        //保存到数据库
        int insert = baseMapper.insert(user);
        if (insert == 1) {
            return Result.ok().message("用户注册成功");
        }
        return Result.error().message("用户注册失败");
    }

    /**
     * 用户登录
     * @param userVo
     * @param request
     * @param response
     * @return
     */
    @Override
    public Result login(LoginUserVo userVo,HttpServletRequest request,HttpServletResponse response) {
        //验证图灵验证码是否正确
        String verifyCode = (String) redisTemplate.opsForValue().get(Constants.User.KEY_CAPTCHA_CODE_CONTENT + userVo.getCaptchaKey());
        if (!userVo.getCaptcha().equalsIgnoreCase(verifyCode)) {
            return Result.error().message("验证码不正确");
        }
        //查询数据库进行验证
        QueryWrapper<TbUser> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("username", userVo.getUsername()).or().eq("email",userVo.getEmail());
        TbUser user = baseMapper.selectOne(userQueryWrapper);
        if (user == null) {
            return Result.error().message("用户名或密码错误");
        }
        boolean matches = bCryptPasswordEncoder.matches(userVo.getPassword(), user.getPassword());
        if (!matches) {
            return Result.error().message("用户名或密码错误");
        }
        //生成token
        createToken(response, user);
        return Result.ok().message("登陆成功");
    }

    /**
     * 生成token，保存到redis和数据库
     * @param response
     * @param user
     * @return
     */
    private String createToken(HttpServletResponse response, TbUser user) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", user.getId());
        claims.put("username", user.getUsername());
        claims.put("roles", user.getRoles());
        claims.put("avatar", user.getAvatar());
        claims.put("email", user.getEmail());
        claims.put("sign", user.getSign());
        String token = JwtUtils.createJwt(claims);
        //返回token的md5值，token会保存到redis里
        String token_key = DigestUtils.md5DigestAsHex(token.getBytes());
        redisTemplate.opsForValue().set(Constants.User.KEY_TOKEN_+token_key,token,60*60*2,TimeUnit.SECONDS);
        //把token_key写到cookie
        CookieUtils.setUpCookie(response,Constants.User.KEY_BLOG_TOKEN,token_key);
        //生成refreshToken
        String refreshTokenValue = JwtUtils.createRefreshToken(user.getId(), Constants.TimeValue.MONTH * 1000);
        //把refreshToken保存到数据库
        TbRefreshToken RefreshToken = new TbRefreshToken();
        RefreshToken.setRefreshToken(refreshTokenValue);
        RefreshToken.setUserId(user.getId());
        RefreshToken.setTokenKey(token_key);
        boolean save = refreshTokenService.save(RefreshToken);
        return token_key;
    }

    /**
     * 检查用户是否登陆，如果登陆就返回用户信息
     * @param request
     * @param response
     * @return
     */
    @Override
    public Map checkUserStatus(HttpServletRequest request, HttpServletResponse response) {
        //拿到前端的token_key
        String token_key = CookieUtils.getTokenInCookie(request, Constants.User.KEY_BLOG_TOKEN);
        if (token_key != null) {
            try {
                //去redis拿到真正的token
                String token = (String)redisTemplate.opsForValue().get(Constants.User.KEY_TOKEN_ + token_key);
                Claims claims = JwtUtils.parseJwt(token);
                TbUser user = JwtUtils.claimsToUser(claims);
                Map<String, Object> map = new HashMap<>();
                map.put("userInfo", user);
                map.put("token_key", token_key);
                return map;
            } catch (Exception e) {
                //解析失败，说明token已经过期
                //去mysql查询refresh_token
                QueryWrapper<TbRefreshToken> refreshTokenQueryWrapper = new QueryWrapper<>();
                refreshTokenQueryWrapper.eq("token_key", token_key);
                TbRefreshToken refreshToken = refreshTokenService.getOne(refreshTokenQueryWrapper);
                //如果refreshToken不存在就是当前用户没有登陆
                if (refreshToken == null) {
                    return null;
                }
                //存在就解析当前refresh_token
                try {
                    Claims claims = JwtUtils.parseJwt(refreshToken.getRefreshToken());
                    //如果有效创建新的token和refresh_token
                    TbUser user = baseMapper.selectById(refreshToken.getUserId());
                    boolean b = refreshTokenService.removeById(refreshToken.getId());
                    String tokenKey = createToken(response, user);
                    String token =(String) redisTemplate.opsForValue().get(Constants.User.KEY_TOKEN_ + tokenKey);
                    Claims claims1 = JwtUtils.parseJwt(token);
                    TbUser responseUser = JwtUtils.claimsToUser(claims1);
                    Map<String, Object> map = new HashMap<>();
                    map.put("userInfo", responseUser);
                    map.put("token_key", tokenKey);
                    return map;
                } catch (Exception e1) {
                    //如果过期，就提示当前用户登陆
                    return null;
                }
            }
        }
        return null;
    }

    /**
     * 检测邮箱是否注册
     * @param email
     * @return
     */
    @Override
    public Result checkEmail(String email) {
        //检查邮箱格式
        boolean validEmail = EmailSender.isValidEmail(email);
        if (!validEmail) {
            return Result.error().message("邮箱格式不正确");
        }
        Integer row = baseMapper.selectByEmail(email);
        if (row == 1) {
            return Result.error().message("该邮箱已经被注册");
        }
        return Result.ok().message("邮箱未被注册");
    }

    /**
     * 检查用户名是否被注册
     * @param username
     * @return
     */
    @Override
    public Result checkUsername(String username) {
        if (username == null) {
            return Result.error().message("用户名不能为空");
        }
        Integer row = baseMapper.selectByUsername(username);
        if (row !=null && row == 1) {
            return Result.error().message("用户名已经存在");
        }
        return Result.ok().message("用户名未被注册");
    }

    /**
     * 更新用户信息
     *
     * @param user
     * @param request
     * @param response
     * @return
     */
    @Override
    public Result updateUser(TbUser user, HttpServletRequest request, HttpServletResponse response) {
        //检查用户是否登陆
        Map map = checkUserStatus(request, response);
        TbUser user_count = (TbUser) map.get("userInfo");
        if (user_count == null) {
            return Result.error().message("用户未登录");
        }
        //判断用户id是否一致
        if (!user_count.getId().equals(user.getId())) {
            return Result.error().message("无权限修改");
        }
        //可以进行修改，头像，签名，用户名
        if (!ObjectUtils.isEmpty(user.getUsername())) {
            //检查是否已经存在
            Result result = checkUsername(user.getUsername());
            if (result.getCode().equals(Result.error().getCode())) {
                return Result.error().message("用户名已经存在");
            }
            user_count.setUsername(user.getUsername());
        }
        if (!ObjectUtils.isEmpty(user.getAvatar())) {
            user_count.setAvatar(user.getAvatar());
        }
        user_count.setSign(user.getSign());
        //更新数据
        int row = baseMapper.updateById(user_count);
        //删除redis里的token
        String token_key = CookieUtils.getTokenInCookie(request, Constants.User.KEY_BLOG_TOKEN);
        redisTemplate.delete(Constants.User.KEY_TOKEN_ + map.get("token_key"));
        return Result.ok().message("更新数据成功");
    }

    /**
     * 根据id删除用户，需要管理员权限
     *
     * @param id
     * @param request
     * @param response
     * @return
     */
    @Override
    public Result deleteUserById(String id, HttpServletRequest request, HttpServletResponse response) {
        if (!JwtUtils.isAdmin(request, response)) {
            return Result.error().message("无权操作");
        }
        int row = baseMapper.deleteById(id);
        if (row == 1) {
            return Result.ok().message("删除用户成功");
        }
        return Result.error().message("删除失败");
    }



    /**
     * 获取用户列表，带分页
     * @param current
     * @param size
     * @param request
     * @param response
     * @return
     */
    @Override
    public Result findUserList(int current, int size, HttpServletRequest request, HttpServletResponse response) {
        if (!JwtUtils.isAdmin(request, response)) {
            return Result.error().message("无权操作");
        }
        //查询数据库
        Page<TbUser> userPage = new Page<>(current,size);
        QueryWrapper<TbUser> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.select("id","username","avatar","email","sign");
        Page<TbUser> tbUserPage = baseMapper.selectPage(userPage, userQueryWrapper);
        List<TbUser> records = tbUserPage.getRecords();
        long total = tbUserPage.getTotal();
        return Result.ok().data("用户列表",records).data("总数",total);
    }

    /**
     * 更新，找回用户密码
     * @param code
     * @param email
     * @param password
     * @return
     */
    @Override
    public Result updateUserPassword(String code,String email,String password) {
        //从redis拿到验证码进行比对
        String verifyCode = (String) redisTemplate.opsForValue().get(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        if (verifyCode == null || !verifyCode.equals(code)) {
            return Result.error().message("验证码错误");
        }
        redisTemplate.delete(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        //对密码进行更改
        try {
            baseMapper.updatePasswordByEmail(bCryptPasswordEncoder.encode(password), email);
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error().message("密码修改失败");
        }
        return Result.ok().message("密码修改成功");
    }

    /**
     * 修改邮箱
     * @param request
     * @param response
     * @param email
     * @return
     */
    @Override
    public Result updateEmail(HttpServletRequest request, HttpServletResponse response, String email,String code) {
        //判断用户是否登陆
        Map map = checkUserStatus(request, response);
        TbUser userInfo = (TbUser)map.get("userInfo");
        if (userInfo == null) {
            return Result.error().message("用户未登录");
        }
        //对新的邮箱进行检测
        Result result = checkEmail(email);
        if (!result.getCode().equals(ResultCodeEnum.SUCCESS.getCode())) {
            return result;
        }
        String verify_code = (String) redisTemplate.opsForValue().get(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        if (!verify_code.equals(code)) {
            return Result.error().message("验证码错误");
        }
        //修改邮箱
        try {
            baseMapper.updateEmailById(email, userInfo.getId());
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error().message("更新失败");
        }
        return Result.ok().message("更新成功");
    }

    /**
     * 退出登录
     * @param request
     * @param response
     * @return
     */
    @Override
    public Result logout(HttpServletRequest request, HttpServletResponse response) {
        //判断用户是否登录
        Map map = checkUserStatus(request, response);
        TbUser userInfo = (TbUser)map.get("userInfo");
        if (ObjectUtils.isEmpty(userInfo)) {
            return Result.error().message("用户未登录");
        }
        //删除redis和mysql中的记录
        String token_key = CookieUtils.getTokenInCookie(request, Constants.User.KEY_BLOG_TOKEN);
        if (ObjectUtils.isEmpty(token_key)) {
            return Result.error().message("用户未登录");
        }
        try {
            redisTemplate.delete(Constants.User.KEY_TOKEN_ + token_key);
            QueryWrapper<TbRefreshToken> RefreshWrapper = new QueryWrapper<>();
            RefreshWrapper.eq("token_key", token_key);
            refreshTokenService.remove(RefreshWrapper);
            //删除cookie里面的token_key
            CookieUtils.deleteCookie(Constants.User.KEY_BLOG_TOKEN,response);
            return Result.ok().message("退出登录成功");
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error().message("程序异常");
        }
    }
}




















